import { Observable, EMPTY } from 'rxjs';
import { concatMap, expand, filter, toArray } from 'rxjs/operators';
import sortBy from 'lodash/sortBy';
import { TTwitchPagination } from './pagination';
import { ITwitchRequestHeaders, TwitchService } from '../twitch';
import { platformAuthorizedRequest } from '../utils';
import { fromPromise } from 'rxjs/internal-compatibility';
import { getPlatformService } from '../index';

/**
 * A tag on Twitch that could be assigned to a Stream.
 */
export type TTwitchTag = {
  tag_id: string;
  /**
   * `true` if the tag is autogenerated. The user is not able to add or remove these.
   */
  is_auto: boolean;
  /** Translations for tag name **/
  localization_names: {
    /**
     * Keys are locale names, in underscore (e.g. `en-us`). Values are the translations.
     */
    [locale: string]: string;
  };
  /** Translations for tag description **/
  localization_descriptions: {
    [locale: string]: string;
  };
};

/**
 * TTwitchTag with a label and description in the user's locale or `en-US` as fallback
 */
export type TTwitchTagWithLabel = TTwitchTag & { name: string; description: string };

/**
 * Response coming from Twitch from the all Tags endpoint.
 */
type TTwitchTagsResponse = {
  /** Response items **/
  data: TTwitchTag[];
  /** Pagination info, including cursor **/
  pagination: TTwitchPagination;
};

/**
 * Intermediate representation of a Twitch tag response so
 * we can request subsequent pages of tags.
 */
interface IPaginatedResponse {
  items: TTwitchTag[];
  cursor: string;
}

/**
 * Map a Twitch tags response to an object containing items and cursor for pagination
 * @param cursor To be used for pagination, the empty string specifies the initial page
 * @see {ITwitchRequestHeaders}
 */
const requestTags = (cursor: string): Observable<IPaginatedResponse> =>
  fromPromise(
    (getPlatformService('twitch') as TwitchService)
      .requestTwitch<{
        data: TTwitchTag[];
        pagination: { cursor: string };
      }>(`https://api.twitch.tv/helix/tags/streams?first=100&after=${cursor}`)
      .then(response => ({
        cursor: response.pagination.cursor,
        items: response.data,
      })),
  );

/**
 * Fetch all available tags that Twitch provides that are not
 * automatically generated.
 *
 * This will use the provided pagination to request the whole dataset of tags.
 *
 * @see https://rxjs-dev.firebaseapp.com/api/operators/expand
 */
export const getAllTags = (): Promise<TTwitchTag[]> =>
  /*
   * Recursively request tags until we get `null` as a cursor, signaling the end of results
   */
  requestTags('')
    .pipe(
      expand(({ cursor }) => (cursor ? requestTags(cursor) : EMPTY)),
      concatMap(({ items }) => items),
      // Auto tags can't be set or unset by the user, so we filter these out
      filter(tag => !tag.is_auto),
      toArray(),
    )
    .toPromise();

/**
 * Fetch all tags for a particular stream
 *
 * @param broadcasterId The stream ID on Twitch
 * @param headers Request headers
 * @see {ITwitchRequestHeaders}
 */
export const getStreamTags = (broadcasterId: string): Promise<TTwitchTag[]> =>
  (getPlatformService('twitch') as TwitchService)
    .requestTwitch<TTwitchTagsResponse>(
      `https://api.twitch.tv/helix/streams/tags?broadcaster_id=${broadcasterId}`,
    )
    .then(res => res.data.filter(tag => !tag.is_auto));

/**
 * Fetch a string translation from the Twitch response, or fallback to `en-us`
 *
 * @param key The key to fetch a translation for
 * @returns Function with the given key preset, so we can add specialized functions below
 * @see {TTwitchTag, getLabelFor, getDescriptionFor}
 */
const getTwitchLocalizedString = (key: string) => (tag: TTwitchTag, locale: string): string => {
  return tag[key][locale.toLowerCase()] || tag[key]['en-us'];
};

/**
 * Get the label (aka name) for a Twitch tag
 */
const getLabelFor = getTwitchLocalizedString('localization_names');

/**
 * Get the tag description for a given tag
 */
const getDescriptionFor = getTwitchLocalizedString('localization_descriptions');

/**
 * Assign name and description to a list of Twitch tags,
 *
 * Get their localized strings from response.
 *
 * @param locale User's locale
 * @param tags A list of tags to assign labels and descriptions to
 */
const assignLabels = (locale: string, tags: TTwitchTag[]): TTwitchTagWithLabel[] =>
  tags.map(tag => ({
    ...tag,
    name: getLabelFor(tag, locale),
    description: getDescriptionFor(tag, locale),
  }));

/**
 * Prepare options for a multiselect component
 *
 * @param locale The user's locale
 * @param tags A list of Twitch Tags
 * @returns A list of Twitch tags with assigned labels and descriptions
 */
export const prepareOptions = (
  locale: string,
  tags: TTwitchTag[] | undefined,
): TTwitchTagWithLabel[] => {
  if (tags && tags.length) {
    const tagsWithLabels = assignLabels(locale, tags);
    return sortBy(tagsWithLabels, 'name');
  }

  return [];
};

/**
 * Update stream tags
 *
 * Replace the set of stream tags with the given tag list `tag_id`'s.
 *
 * @returns Function that takes the list of tags to update and returns a promise of the request.
 * @see {ITwitchRequestHeaders}
 */
export const updateTags = () => (tags: TTwitchTag[]) => (streamId: string) =>
  platformAuthorizedRequest('twitch', {
    url: `https://api.twitch.tv/helix/streams/tags?broadcaster_id=${streamId}`,
    method: 'PUT',
    body: JSON.stringify({ tag_ids: tags ? tags.map(tag => tag.tag_id) : [] }),
  });
